package com.jzo2o.market.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.CommonUserApi;
import com.jzo2o.api.market.dto.request.CouponUseBackReqDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.JsonUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.market.constants.RedisConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.enums.CouponStatusEnum;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.CouponOperationPageQueryReqDTO;
import com.jzo2o.market.model.dto.request.SeizeCouponReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUseBackService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.mysql.utils.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    @Resource(name = "seizeCouponScript")
    private DefaultRedisScript<String> seizeCouponScript;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private IActivityService activityService;

    @Resource
    private ICouponUseBackService couponUseBackService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    @Autowired
    private CommonUserApi commonUserApi;

    @Override
    public PageResult<CouponInfoResDTO> pageQuery(CouponOperationPageQueryReqDTO dto) {
        Page<Coupon> couponPage = PageUtils.parsePageQuery(dto, Coupon.class);

        Page<Coupon> page = this.lambdaQuery()
                .eq(Coupon::getActivityId, dto.getActivityId())
                .page(couponPage);

        return PageUtils.toPage(page, CouponInfoResDTO.class);
    }

    @Override
    public List<CouponInfoResDTO> my(Integer status, Integer lastId) {
        Long userId = UserContext.currentUserId();

        List<Coupon> list = this.lambdaQuery()
                .eq(Coupon::getUserId, userId)
                .eq(Coupon::getStatus, status)
                .eq(!ObjectUtil.isNull(lastId), Coupon::getId, lastId)
                .list();

        return list.stream().map(c-> BeanUtil.toBean(c,  CouponInfoResDTO.class)).collect(Collectors.toList());
    }

    @Override
    public void seizeCoupon(SeizeCouponReqDTO seizeCouponReqDTO) {
        Long id = seizeCouponReqDTO.getId();

        //1. 查询优惠券活动，判断是否存在
        ActivityInfoResDTO dto = getCouponFromCache(id);

        //2. 判断优惠券的状态是否有效（非发放中都不行）
        if(ObjectUtil.notEqual(activityService.getCurrentRealStatus(dto), ActivityStatusEnum.DISTRIBUTING.getStatus())){
            throw new ForbiddenOperationException("活动非法状态异常");
        }

        //3. 判断优惠券活动的时间段是否有效（未开始或已结束都不行）
        if(dto.getDistributeStartTime().isAfter(DateUtils.now()) || dto.getDistributeEndTime().isBefore(DateUtils.now())){
            throw new ForbiddenOperationException("非法时间段");
        }

        //4. 准备Lua脚本的key和argv
        Long activityId = id;//ARGV[1] 活动id
        Long userId = UserContext.currentUser().getId(); //ARGV[2] 用户id
        List keys = Arrays.asList(
                //KEYS[1] 抢券同步队列 RedisKey
                String.format(RedisConstants.RedisKey.COUPON_SEIZE_SYNC_QUEUE_NAME, activityId%10),
                //KEYS[2] 优惠券活动库存 RedisKey
                String.format(RedisConstants.RedisKey.COUPON_RESOURCE_STOCK, activityId%10),
                //KEYS[3] 抢券成功队列 RedisKey
                String.format(RedisConstants.RedisKey.COUPON_SEIZE_LIST, activityId, activityId%10)
        );

        //5. 调用lua脚本执行抢券处理
        Object result = redisTemplate.execute(seizeCouponScript, keys, activityId, userId);

        //6. 根据Lua脚本执行结果进行处理
        if(ObjectUtils.isNull(result)) {
            throw new CommonException("抢券失败");
        }
        Long code = Long.valueOf(result.toString());
        if(code>0) {
            return; //抢券成功
        }
        if(code==-1) {
            throw new CommonException("限领一张");
        }

        if(code==-2 || code==-4) {
            throw new CommonException("已抢光");
        }

        if(code==-3 || code==-5) {
            throw new CommonException("抢券失败");
        }
    }

    @Override
    public List<AvailableCouponsResDTO> getAvailable(BigDecimal totalAmount) {

        // 属于当前用户、状态为未使用、总金额 小于 满减额度、没过期
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getUserId, UserContext.currentUserId())
                .eq(Coupon::getStatus, CouponStatusEnum.NO_USE.getStatus())
                .lt(Coupon::getAmountCondition, totalAmount)
                .gt(Coupon::getValidityTime, DateUtils.now())
                .list();

        if(CollUtils.isEmpty(couponList)){
            return CollUtils.emptyList();
        }

        //
        List<AvailableCouponsResDTO> resDTOList = couponList.stream()
                .peek(x -> x.setDiscountAmount(CouponUtils.calDiscountAmount(x, totalAmount)))
                .filter(x -> x.getDiscountAmount().compareTo(new BigDecimal("0")) > 0 && x.getDiscountAmount().compareTo(totalAmount) <= 0)
                .map(x -> BeanUtil.copyProperties(x, AvailableCouponsResDTO.class))
                .sorted(Comparator.comparing(AvailableCouponsResDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());

        return resDTOList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CouponUseResDTO use(CouponUseReqDTO dto) {
        Long ordersId = dto.getOrdersId(); //订单id
        BigDecimal totalAmount = dto.getTotalAmount();//总金额
        Long couponId = dto.getId();//优惠券id

        //1. 判断参数是否为空
        if(ObjectUtils.isNull(ordersId) || ObjectUtils.isNull(totalAmount) || ObjectUtils.isNull(couponId)) {
            throw new BadRequestException("非法参数");
        }

        //2. 判断是否我的优惠券
        Coupon coupon = this.getById(couponId);
        //2.1 优惠券是否存在
        if(ObjectUtils.isNull(coupon)) {
            throw new CommonException("优惠券不存在");
        }

        Long userId = UserContext.currentUserId();

        //2.2 是否我的优惠券
        if(ObjectUtils.notEqual(userId,coupon.getUserId())) {
            throw new CommonException("只能核销自己的优惠券");
        }

        //2.3 优惠券是否未使用
        if(ObjectUtils.notEqual(coupon.getStatus(),CouponStatusEnum.NO_USE.getStatus())) {
            throw new CommonException("优惠券状态非法");
        }

        //2.4 优惠券是否还未过期
        if(DateUtils.now().isAfter(coupon.getValidityTime())) {
            throw new CommonException("优惠券已过期");
        }

        //2.5 总金额是否大于等于满减金额
        if(coupon.getAmountCondition().compareTo(totalAmount)>0) {
            throw new CommonException("满减金额不能超过订单总额");
        }


        //3. 更新优惠券 update coupon set status=2,order_id=?,use_time=? where id=?
        boolean resultForCoupon = this.lambdaUpdate()
                .eq(Coupon::getId, couponId)
                .set(Coupon::getStatus, CouponStatusEnum.USED.getStatus())
                .set(Coupon::getOrdersId, ordersId)
                .set(Coupon::getUseTime, DateUtils.now())
                .update();
        if(!resultForCoupon) {
            throw new CommonException("核销优惠券失败");
        }

        //4. 新增优惠券核销记录
        CouponWriteOff couponWriteOff = new CouponWriteOff();
        couponWriteOff.setOrdersId(ordersId);
        couponWriteOff.setCouponId(couponId);
        couponWriteOff.setActivityId(coupon.getActivityId());
        couponWriteOff.setUserId(userId);
        couponWriteOff.setWriteOffManName(coupon.getName());
        couponWriteOff.setWriteOffManPhone(coupon.getUserPhone());
        couponWriteOff.setWriteOffTime(DateUtils.now());
        boolean resultForWriteOff = couponWriteOffService.save(couponWriteOff);
        if(!resultForWriteOff) {
            throw new CommonException("核销优惠券失败");
        }

        CouponUseResDTO couponUseResDTO = new CouponUseResDTO();
        couponUseResDTO.setDiscountAmount(CouponUtils.calDiscountAmount(coupon,totalAmount));
        return couponUseResDTO;

    }

    @Override
    public void useBack(CouponUseBackReqDTO couponUseBackReqDTO) {

    }


    private ActivityInfoResDTO getCouponFromCache(Long id) {

        Object jsonStr = redisTemplate.opsForValue().get(RedisConstants.RedisKey.ACTIVITY_CACHE_LIST);
        if(ObjectUtil.isNull(jsonStr)){
            return null;
        }

        List<ActivityInfoResDTO> activityInfoResDTOS = JsonUtils.toList(jsonStr.toString(), ActivityInfoResDTO.class);
        return activityInfoResDTOS.stream().filter(x -> ObjectUtil.equal(x.getId(), id)).findFirst().orElse(null);
    }
}
